<?php
	/**
	  * @package	MyTools
	  * @name		NomberConverter
	  * @brief		Clase para convertir numeros a letras
	  * @author		Hermann D. Schimpf <hschimpf@gschimpf.com>
	  * @license	http://opensource.org/licenses/gpl-2.0.php
	  * @date		2010-01-04
	  * @version	0.2.8
  	**/

	final class NumberConverter extends MyDebuger implements MyVersions {
		/* version de la clase */
		private static $version			= 0;
		/* subversion de la clase */
		private static $subversion		= 2;
		/* numero de revision de la clase */
		private static $revision		= 8;

		/* Matrices para las Unidades */
		protected $unidad		= Array();
		protected $subUnidad	= Array();
		/* Matrices para las Decenas */
		protected $decena		= Array();
		protected $subDecena	= Array();
		protected $miles		= Array();
		/* Variables internas */
		protected $negativo	= 'menos ';
		protected $numero		= '';
		protected $enLetras	= '';

		protected $zeros	= True;
		protected $punto	= False;

		protected $decimales	= True;
		protected $femenino	= False;

		public function __construct($debug = False) {
			/* creamos el padre */
			parent::__construct($debug, __CLASS__);
		}

		public static function getVersion() {
			/* retornamos la version actual de la clase */
			return 'v' . self::$version . '.' . self::$subversion . '.' . self::$revision;
		}

		public static function convertToWords($number = Null, $female = False, $debug = False) {
			try {
				try {
					/* creamos la instancia */
					$num2letra = new NumberConverter($debug);
					/* verificamos si especifico un numero */
					if ($number === Null)
						/* salimos con una excepcion */
						$num2letra->newException('No se especifico el numero a convertir');
					/* verificamos si especifico un numero */
					if (gettype($number) !== 'integer' && gettype($number) !== 'double' && gettype($number) !== 'string')
						/* salimos con una excepcion */
						$num2letra->newException('La cadena a convertir debe ser un numero');
					/* verificamos que la bandera en femenino sea booleana */
					if (gettype($female) !== 'boolean')
						/* salimos con una excepcion */
						$num2letra->newException('La bandera de femenino debe ser booleana');
					/* cargamos las matrices */
					$num2letra ->loadMatrix();
					/* retornamos el numero convertido */
					return $num2letra->convertir($number, $female);
				} catch (Exception $e) {
					/* capturamos todos los errores y los transformamos en mi excepcion */
					$num2letra->convertToMyException($e);
				}
			} catch (MyException $e) {
				/* si se produjo una excepcion la manejamos con el metodo 'show' */
				$num2letra->show($e->getMensaje());
			}
		}

		private function loadMatrix() {
			/* cargamos las matrices de unidad */
			$this->loadUnidad();
			/* cargamos las matrices de decenas */
			$this->loadDecena();
			/* cargamos las matrices de miles */
			$this->loadMiles();
		}

		protected function loadUnidad() {
			/* mostramos un mensaje */
			$this->show('Cargando matriz de unidades..', False);
 			/* cargamos la primera matriz */
 			$this->loadUnidades();
 			/* cargamos la subMatriz */
 			$this->loadSubUnidades();
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function loadUnidades() {
			/* armamos un array temporal con los valores */
			$unidades = Array('dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve', 'diez', 'once', 'doce', 'trece', 'catorce', 'quince');
			for ($i = 2; $i < 16; $i++)
				/* cargamos la matriz 'unidades' en el atributo 'unidad' con sus respectivos lugares */
				$this->unidad[$i] = $unidades[($i - 2)];
			for ($i = 6; $i < 10; $i++)
				/* del 16 al 19 va precedida la palabra 'dieci' */
				$this->unidad[($i + 10)] = 'dieci' . $this->unidad[$i];
			/* cargamos el solitario 'veinte' */
			$this->unidad[20]	= 'veinte';
		}

		private function loadSubUnidades() {
			/* armamos un array temporal con los valores */
			$subUnidades = Array('quin', 'seis', 'sete', 'ocho', 'nove');
			/* cargamos las subUnidades */
			for ($i = 2; $i < 5; $i++)
				/* del 2 al 4 son iguales a la unidad */
				$this->subUnidad[$i] = $this->unidad[$i];
			for ($i = 5; $i < 10; $i++)
				/* cargamos la matriz 'unidades' en el atributo 'unidad' con sus respectivos lugares */
				$this->subUnidad[$i] = $subUnidades[($i - 5)];
		}

		private function loadDecena() {
			/* mostramos un mensaje */
			$this->show('Cargando matriz de decenas..', False);
			/* cargamos la primera matriz */
			$this->loadDecenas();
			/* cargamos la segunda matriz */
			$this->loadSubDecenas();
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function loadDecenas() {
			/* armamos un array temporal */
			$decenas = Array('veint', 'treinta', 'cuarenta', 'cincuenta', 'sesenta', 'setenta', 'ochenta', 'noventa');
			/* ingresamos a un loop */
			for ($i = 2; $i < 10; $i++)
				/* cargamos la matriz temporal en el atributo 'decena' con sus respectivos lugares */
				$this->decena[$i] = $decenas[($i - 2)];
		}

		private function loadSubDecenas() {
			/* armamos un array temporal */
			$subDecenas = Array('mill', 'bill', 'mill', 'trill', 'mill', 'bill', 'mill');
			for ($i = 3; $i <= 15; $i++) {
				/* cargamos la matriz temporal en el atributo 'subDecena' con sus respectivos logares */
				$this->subDecena[$i] = $subDecenas[($i / 3)];
				/* saltamos un valor */
				$i++;
			}
		}

		protected function loadMiles() {
			/* mostramos un mensaje */
			$this->show('Cargando matriz de miles..', False);
			/* armamos un array temporal */
			$miles = Array('trillones', 'de trillones', 'millones de trillones', 'de trillones', 'billones de trillones', 'de billones de trillones', 'millones de billones de trillones');
			/* cargamos los valores solitarios */
			$this->miles[4] = 'millones';
			$this->miles[6] = 'billones';
			$this->miles[7] = 'de billones';
			$this->miles[8] = 'millones de billones';
			for ($i = 10; $i < 17; $i++)
				/* recorremos la matriz temporal almacenando los valores en el atributo 'miles' */
				$this->miles[$i] = $miles[($i - 10)];
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function convertir($numero, $female = False) {
			/* almacenamos el numero */
			$this->numero = number_format($numero, 3, ',', '');
			/* mostramos un mensaje */
			$this->show('Convirtiendo el numero ' . $this->italic($this->numero) . '..');
			/* almacenamos la bandera de femenino */
			$this->femenino = $female;
			/* convertimos el numero */
			$this->convert2letras();
			/* retornamos el numero convertido */
			return $this->getLetras();
		}

		private function convert2letras() {
			/* limpiamos los espacios */
			$this->trimSpaces();
			/* verificamos si es negativo */
			$this->negativo();
			/* eliminamos los ceros de la cadena */
			$this->eliminarCeros();
			/** nose que hace esto.. **/
			$this->hacerAlgo();
			/* ejecutamos la conversion */
			$this->iniciarConvertir();
		}

		private function trimSpaces() {
			/* mostramos un mensaje */
			$this->show('Eliminando espacios del numero..', False);
			/* eliminamos los espacios y convertimos la cadena a string */
			$this->numero = trim((string)@$this->numero);
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function negativo() {
			/* mostramos un mensaje */
			$this->show('Verificando signo negativo..', False);
			/* verificamos si tiene el signo negativo */
			if ($this->numero[0] == '-')
				/* si tiene el signo negativo de lo eliminamos */
				$this->eliminarCaracter();
			else
				/* si no tiene el signo negativo vaciamos el atributo 'negativo' */
				$this->negativo = '';
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function eliminarCaracter() {
			/* mostramos un mensaje */
			$this->show('Eliminando caracter ' . $this->italic(substr($this->numero, 0, 1)) . '..', False);
			/* eliminamos un caracter al inicio */
			$this->numero = substr($this->numero, 1);
		}

		private function eliminarCeros() {
			/* mostramos un mensaje */
			$this->show('Buscando ceros al inicio del numero..', False);
			/* ingresamos a un loop mientras existan ceros al inicio */
			while ($this->numero[0] == '0')
				/* mientras que el primer caracter sea cero lo quitamos */
				$this->eliminarCaracter();
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function hacerAlgo() {
			/* no entiendo para que sirve esto */
			if ($this->numero[0] < '1' || $this->numero[0] > 9)
				$this->numero = '0' . $this->numero;
		}

		private function iniciarConvertir() {
			/* mostramos un mensaje */
			$this->show('Iniciando conversion del numero..');
			/* procesamos la parte entera */
			$this->separarPartes();
			/* buscamos si es un valor con coma y lo procesamos */
			$this->procesarFraccion();
			/* verificamos si la parte entera es cero */
			$this->verificarCero();
			/* juntamos y procesamos la parte entera con la coma */
			$this->juntarProcesar();
			/* finalizamos la conversion */
			$this->finalizar();
		}

		private function getLetras() {
			/* retornamos la cadena convertida */
			return $this->enLetras;
		}

		private function separarPartes() {
			/* mostramos un mensaje */
			$this->show('Obteniendo parte entera..', False);
			/* recorremos cada numero de la cadena */
			for ($c = 0; $c < strlen($this->numero); $c++) {
				/* obtenemos el numero */
				$numero = $this->numero[$c];
				/* buscamos un punto */
				if (!(strpos(".,'''", $numero) === False)) {
					if ($this->punto)
						break;
					else {
						/* si hay un punto habilitamos la bandera */
						$this->punto = True;
						continue;
					}
				} elseif (!(strpos('0123456789', $numero) === False)) {
					if ($this->punto) {
						/* si la bandera esta habilitada y el numero no es un cero */
						if ($numero != '0')
							$this->zeros = False;
						/* agregamos el numero a la variable que contendra la fraccion */
						$this->fraccion .= $numero;
					} else
						/* si no hay un punto agregamos el numero a la parte entera */
						$this->entero .= $numero;
				} else
					break;
			}
			/* agregamos espacios al frente. Se utiliza mas adelante */
			$this->entero = '     ' . $this->entero;
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function procesarFraccion() {
			/* mostramos un mensaje */
			$this->show('Procesando valores de la fraccion..', False);
			/* verificamos si tiene decimales */
			if ($this->decimales && $this->fraccion && !$this->zeros) {
				/* si tiene decimales y no son ceros agregamos la palabra 'coma' */
				$this->fin = ' coma';
				/* recorremos lo que hay luego de la coma */
				for ($n = 0; $n < strlen($this->fraccion); $n++) {
					if (($s = $this->fraccion[$n]) == '0')
						/* si es cero agregamos la palabra 'cero' */
						$this->fin .= ' cero';
					elseif ($s == '1')
						/* sino, si es 1, de acuerdo al parametro usamos terminacion femenina o masculina */
						$this->fin .= $this->femenino ? ' una' : ' uno';
					else
						/* sino, agregamos el numero desde la matriz */
						$this->fin .= ' ' . $this->unidad[$s];
				}
			} else
				/* si no hay parte fraccion eliminamos el final */
				$this->fin = '';
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function verificarCero() {
			/* verificamos si la parte entera es cero */
			if ((int)$this->entero === 0)
				/* si la parte entera es 0 (cero), reemplazamos por la palabla cero mas el final */
				$this->enLetras = ' cero ' . $this->fin;
		}

		private function juntarProcesar() {
			$sub	= 0;
			$mils	= 0;
			/* mostramos un mensaje */
			$this->show('Procesando partes del numero..', False);
			/* recorremos la cadena mientras que no queden espacios */
			while (($this->numero = substr($this->entero, -3)) != '   ') {
				$this->entero = substr($this->entero, 0, -3);
				if (++$sub < 3 && $this->femenino) {
					$this->unidad[1] = 'una';
					$subcent = 'as';
				} else {
					$this->unidad[1] = $this->femenino ? 'una' : 'uno';
					$subcent = 'os';
				}
				$parteLetras = '';
				$decena = substr($this->numero, 1);
				if ($decena == '00') {} elseif ($decena < 21)
					$parteLetras = ' ' . $this->unidad[(int)$decena];
				elseif ($decena < 30) {
					$unidad = $this->numero[2];
					if ($unidad != 0)
						$parteLetras = 'i' . $this->unidad[$unidad];
					$decena = $this->numero[1];
					$parteLetras = ' ' . $this->decena[$decena] . $parteLetras;
				} else {
					$unidad = $this->numero[2];
					if ($unidad != 0)
						$parteLetras = ' y ' . $this->unidad[$unidad];
					$decena = $this->numero[1];
					$parteLetras = ' ' . $this->decena[$decena] . $parteLetras;
				}
				$centena = $this->numero[0];
				if ($centena == 1)
					$parteLetras = ' ciento' . $parteLetras;
				elseif ($centena == 5)
					$parteLetras = ' ' . $this->subUnidad[$centena] . 'ient' . $subcent . $parteLetras;
				elseif ($centena != 0)
					$parteLetras = ' ' . $this->subUnidad[$centena] . 'cient' . $subcent . $parteLetras;
				if ($sub == 1) {} elseif (!isset($this->subDecena[$sub])) {
					if ($this->numero == 1)
						$parteLetras = ' mil';
					elseif ($this->numero > 1)
						$parteLetras .= ' mil';
				} elseif ($this->numero == 1)
					$parteLetras .= ' ' . $this->subDecena[$sub] . '&oacute;n';
				elseif ($this->numero > 1)
					$parteLetras .= ' ' . $this->subDecena[$sub] . 'ones';
				if ($this->numero == '000')
					$mils ++;
				elseif ($mils != 0) {
					if (isset($this->miles[$sub]))
						$parteLetras .= ' ' . $this->miles[$sub];
					$mils = 0;
				}
				$this->enLetras = $parteLetras . $this->enLetras;
			}
			/* mostramos ok al mensaje */
			$this->showOK();
		}

		private function finalizar() {
			/* mostramos un mensaje */
			$this->show('Arnamdo cadena final (negativo + letras + fraccion)..', False);
			/* armamos la cadena agregando el signo negativo, el numero y la parte fraccion */
			$this->enLetras = $this->negativo . substr($this->enLetras, 1) . $this->fin;
			/* pasamos a minuscilas */
			$this->enLetras = strtolower($this->enLetras);
			/* mostramos ok al mensaje */
			$this->showOK();
			/* mostramos el resultado final */
			$this->show('Resultado final: ' . $this->italic($this->bold($this->enLetras)));
		}
	}
?>